package com.yilin.platform.base_core.interceptor;

import com.google.gson.reflect.TypeToken;
import com.yilin.platform.base_core.commons.encrypt.EncryptUtils;
import com.yilin.platform.base_core.commons.utils.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * 过滤器：请求数据重构
 */
@Component
//@WebFilter(filterName = "decryptFilter", urlPatterns = "/*")
@Order(1) //测试好像这个参数不生效，实际生效的是Filter扫描到的顺序（所以起名很重要）
public class ReqBindingFilter implements Filter {
    public static Logger logger = LoggerFactory.getLogger(ReqBindingFilter.class);
    private final static String UPLOAD = "upload";
    private final static String ContentType = "Content-Type";

    @Value("${yilin-base-url}")
    private String yilinBaseUrl;//易林平台基础服务地址

    private WebApplicationContext applicationContext;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        ServletContext servletContext = filterConfig.getServletContext();
        applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        String uri = httpRequest.getRequestURI();

        //转发到易林服务器
        if(!uri.startsWith("/platform/") && !uri.startsWith("/xbr/")){
            //转发关键头部数据，有效好像只有下面几个
            Map<String, String> headerMap = new HashMap<>();
            headerMap.put("token",httpRequest.getHeader("token"));
            headerMap.put("upload",httpRequest.getHeader("upload"));
            headerMap.put("deviceId",httpRequest.getHeader("deviceId"));
            if (StringUtil.isNotEmpty(httpRequest.getHeader("mobile"))){
                headerMap.put("mobile",httpRequest.getHeader("mobile"));
            }
            //转发所有参数
            Map<String, String> parameterMap =  HttpService.httpParameter2Map(httpRequest);
            //判断是不是上传文件
            String upload = httpRequest.getHeader(UPLOAD);
            String contentType = httpRequest.getHeader(ContentType);
            if (Boolean.parseBoolean(upload) || (contentType!=null && contentType.contains("multipart/form-data"))){
                List<MultipartFile> multipartFiles = HttpService.httpMultipartFiles(httpRequest);
                String retDataJson = HttpService.httpPostWithFile(String.format("%s%s",yilinBaseUrl,uri),multipartFiles,headerMap,parameterMap);
                responseData( httpRequest,httpResponse, retDataJson,httpRequest.getContentType());
            }else{
                String bodyData = StreamUtil.getBodyString(httpRequest.getInputStream(),StandardCharsets.UTF_8);
                String retDataJson = HttpService.httpPostWithJson(String.format("%s%s",yilinBaseUrl,uri),bodyData,headerMap);
                responseData( httpRequest,httpResponse, retDataJson,httpRequest.getContentType());
            }
            return;
        }

        // 获取 HandlerMapping
        RequestMappingHandlerMapping handlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Method handlerMethod = getHandlerMethod(handlerMapping, httpRequest);
        if (handlerMethod != null && handlerMethod.isAnnotationPresent(NoBindingFilter.class)) {
            // 方法带有 @NoBindingFilter 注解，直接执行下一个过滤器或处理器
            chain.doFilter(request, response);
            return;
        }

        // 执行过滤器逻辑
        request = new ReqBindingFilterWrapper(httpRequest);
        chain.doFilter(request, response);
    }

    // 获取请求的处理方法
    private Method getHandlerMethod(RequestMappingHandlerMapping handlerMapping, HttpServletRequest request) {
        HandlerExecutionChain handlerExecutionChain;
        try {
            handlerExecutionChain = handlerMapping.getHandler(request);
            if (handlerExecutionChain != null) {
                Object handler = handlerExecutionChain.getHandler();
                if (handler instanceof HandlerMethod) {
                    return ((HandlerMethod) handler).getMethod();
                }
            }
        } catch (Exception e) {
            // 处理异常
        }
        return null;
    }

    //返回前台数据
    private void responseData(HttpServletRequest request,HttpServletResponse response,String retDataJson,String contentType) {
        try {
            response.setHeader("Access-Control-Allow-Origin",request.getHeader("origin"));
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Headers", "content-type, token, upload");
            response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
            response.setCharacterEncoding("UTF-8");//设置字符集为'UTF-8'
            if (contentType==null) contentType = ("text/html;charset=utf-8");
            response.setContentType(contentType);//设置格式为text/json 
            PrintWriter write = response.getWriter();
            write.write(retDataJson);
            write.flush();
            write.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 过滤器：数据重构 JSON
     */
    private static class ReqBindingFilterWrapper extends HttpServletRequestWrapper {
        private Map<String, Object> bodyMap = new HashMap<>();
        boolean original = false;
        ReqBindingFilterWrapper(HttpServletRequest request) throws IOException {
            super(request);
            ///---编码
            Charset encoding = StandardCharsets.UTF_8;
            String contentType = request.getHeader("content-type");
            if (contentType != null && contentType.contains("charset=GBK")) encoding = Charset.forName("GBK");
            ///---设置编码
            request.setCharacterEncoding(encoding.name());
            boolean isJson = contentType!=null && contentType.contains("application/json");
            boolean isEncrypt = Boolean.parseBoolean(request.getHeader("isEncrypt"));
            String tokenID = request.getHeader("token");
            //第一种情况，使用表单直接上送，这种情况下，得到的bodyData是字符串
            String bodyData = request.getParameter("body");
            //第二种情况，使用"application/json"上送，这种情况得到的是JSON，需从里面找到bodyData
            if (isJson &&  StringUtil.isEmpty(bodyData)) {
                bodyData = StreamUtil.getBodyString(request.getInputStream(),encoding);
                if (JsonUtil.isJson(bodyData)) {
                    Map<String, Object> map = JsonUtil.jsonToObject(bodyData, new TypeToken<Map<String, Object>>() {});
                    if (map != null && map.containsKey("body")) bodyData = (String) map.get("body");
                }
            }
            //这里只要是JSON就一定是加密的，或者采用前端传过来的isEncrypt值判定
            //（！值得注意的是银行传过来的值是xml格式的，并且是GBK编码，这里想办法不要拦截 走 original=true）
            if (StringUtil.isNotEmpty(bodyData) && (isJson||isEncrypt)) {
                String jsonData = EncryptUtils.aesDecrypt(bodyData, tokenID);
                if (JsonUtil.isJson(jsonData)) this.bodyMap = JsonUtil.jsonToObject(jsonData, new TypeToken<Map<String, Object>>() {});
                setChildMap(bodyMap);
            } else {
                original = true;
            }
        }

        //MAP实体子类转换
        @SuppressWarnings("unchecked")
        private void setChildMap(Map<String, Object> paramMap) {
            if (paramMap == null) {
                return;
            }
            List<String> mapKeys = new ArrayList<>();
            List<String> listKeys = new ArrayList<>();
            boolean hasMap = false;
            boolean hasList = false;
            for (String paramKey : paramMap.keySet()) {
                if (paramMap.get(paramKey) == null) {
                } else if (paramMap.get(paramKey) instanceof Map) {
                    mapKeys.add(paramKey);
                    hasMap = true;
                } else if (paramMap.get(paramKey) instanceof List) {
                    listKeys.add(paramKey);
                    hasList = true;
                } else if (paramMap.get(paramKey) instanceof Double) {
                    paramMap.put(paramKey, CommonUtil.trimNumber(String.valueOf(paramMap.get(paramKey))));
                } else {
                    paramMap.put(paramKey, String.valueOf(paramMap.get(paramKey)));
                }
            }
            for (String removeKey : mapKeys) {
                Map<String, Object> childMap = (Map<String, Object>) paramMap.get(removeKey);
                for (String childKey : childMap.keySet()) {
                    paramMap.put(removeKey + "." + childKey, childMap.get(childKey));
                }
                paramMap.remove(removeKey);
            }
            for (String removeKey : listKeys) {
                List<Object> list = (List<Object>) paramMap.get(removeKey);
                for (int i = 0; i < list.size(); i++) {// "obh.list.0.sss"
                    paramMap.put(removeKey + "[" + i + "]", list.get(i));
                }
                paramMap.remove(removeKey);
            }
            if (hasMap || hasList) {
                setChildMap(paramMap);
            }
        }

        //返回所有参数名
        @SuppressWarnings("rawtypes")
        @Override
        public Enumeration getParameterNames() {
            if (original) return super.getParameterNames();
            if (bodyMap == null) return null;
            setChildMap(bodyMap);
            Vector<String> names = new Vector<>(this.bodyMap.keySet());
            return names.elements();
        }

        //获取所有参数值
        @Override
        public String[] getParameterValues(String name) {
            if (original) return super.getParameterValues(name);
            if (bodyMap == null || bodyMap.get(name) == null || StringUtil.isEmpty(bodyMap.get(name).toString())) {
                return null;
            }
            String data = bodyMap.get(name).toString();
            return new String[]{data};
        }

    }
}
